/* Copyright (c) 2008, RDFfs - Andrea Marchesini <baku@morlardf.net>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
# error Use configure; make; make install
#endif

#include <rdffs.h>

/* PARSER ******************************************************************/
static gint rdffs_parse_log (void *dummy, const char *msg, va_list va);
static gchar *rdffs_parse_trim (gchar * tmp);
static void rdffs_parse_stream (librdf_stream * stream);
static gboolean rdffs_parse_stream_node (gchar ** item, gsize * itemsize,
					 enum rdffs_object_t *object,
					 gchar ** lang, gsize * langsize,
					 gchar ** datatype,
					 gsize * datatypesize,
					 librdf_node * node);
static void rdffs_parse_link (GList * list);
static void rdffs_parse_triple_link (struct rdffs_triple_t *triple);
static void rdffs_parse_resource_link (gchar * string,
				       struct rdffs_link_t *link,
				       gboolean is_bnode);

gboolean
rdffs_parse (gchar * buffer, gchar * file, enum rdffs_format_t format,
	     gchar ** error, gsize * error_size)
{
  librdf_world *world;
  librdf_storage *storage;
  librdf_parser *parser;
  librdf_model *model;
  librdf_stream *stream;
  librdf_uri *uri;
  GString *errstring = NULL;

  world = librdf_new_world ();
  librdf_world_open (world);

  librdf_world_set_error (world, &errstring, rdffs_parse_log);
  librdf_world_set_warning (world, &errstring, rdffs_parse_log);

  if (!(uri = librdf_new_uri_from_filename (world, file)))
    {
      librdf_free_world (world);
      return FALSE;
    }

  if (!(storage = librdf_new_storage (world, "memory", NULL, NULL)))
    {
      librdf_free_uri (uri);
      librdf_free_world (world);
      return FALSE;
    }

  if (!(model = librdf_new_model (world, storage, NULL)))
    {
      librdf_free_storage (storage);
      librdf_free_uri (uri);
      librdf_free_world (world);
      return FALSE;
    }

  switch (format)
    {
    case RDF_FORMAT_TURTLE:
      parser = librdf_new_parser (world, "turtle", NULL, NULL);
      break;

    case RDF_FORMAT_NTRIPLES:
      parser = librdf_new_parser (world, "ntriples", NULL, NULL);
      break;

    default:
    case RDF_FORMAT_XML:
      parser = librdf_new_parser (world, "rdfxml", NULL, NULL);
      break;
    }

  if (!parser)
    {
      librdf_free_model (model);
      librdf_free_storage (storage);
      librdf_free_uri (uri);
      librdf_free_world (world);
      return FALSE;
    }

  if (librdf_parser_parse_string_into_model
      (parser, (unsigned char *) buffer, uri, model))
    {
      librdf_free_parser (parser);
      librdf_free_model (model);
      librdf_free_storage (storage);
      librdf_free_uri (uri);
      return FALSE;
    }

  if (!(stream = librdf_model_as_stream (model)))
    {
      librdf_free_parser (parser);
      librdf_free_model (model);
      librdf_free_storage (storage);
      librdf_free_uri (uri);
      return FALSE;
    }

  rdffs_parse_stream (stream);

  librdf_free_stream (stream);
  librdf_free_parser (parser);
  librdf_free_model (model);
  librdf_free_storage (storage);
  librdf_free_uri (uri);
  librdf_free_world (world);

  if (errstring)
    {
      rdffs_destroy_data ();

      *error_size = errstring->len;
      *error = g_string_free (errstring, FALSE);
      return FALSE;
    }

  rdffs_parse_link (rdffs_input.subjects);
  rdffs_parse_link (rdffs_input.bnodes);

  return TRUE;
}

static gint
rdffs_parse_log (void *dummy, const char *msg, va_list va)
{
  GString **string = dummy;
  if (!*string)
    *string = g_string_new (NULL);

  g_string_append_vprintf (*string, msg, va);
  return 1;
}

static gchar *
rdffs_parse_trim (gchar * tmp)
{
  gchar *ret;
  gint i, j, q;
  gint size;

  i = 0;
  while (g_ascii_isspace (tmp[i]) == TRUE)
    tmp++;

  i = strlen (tmp);
  i--;

  while (g_ascii_isspace (tmp[i]) == TRUE)
    i--;

  tmp[i + 1] = 0;

  size = strlen (tmp);
  ret = g_malloc (sizeof (gchar) * (size + 1));

  for (q = i = j = 0; i < size; i++)
    {
      if (*(tmp + i) == 0xD)
	continue;

      if (g_ascii_isspace (*(tmp + i)) == TRUE)
	{
	  if (!q)
	    {
	      q = 1;
	      ret[j++] = *(tmp + i);
	    }
	}

      else
	{
	  q = 0;
	  ret[j++] = *(tmp + i);
	}
    }

  ret[j] = 0;
  tmp = g_strdup (ret);
  g_free (ret);

  return tmp;
}

static gboolean
rdffs_parse_stream_node (gchar ** item, gsize * itemsize,
			 enum rdffs_object_t *object, gchar ** lang,
			 gsize * langsize, gchar ** datatype,
			 gsize * datatypesize, librdf_node * node)
{
  librdf_uri *uri;

  switch (librdf_node_get_type (node))
    {
      /* Resource: */
    case LIBRDF_NODE_TYPE_RESOURCE:
      uri = librdf_node_get_uri (node);
      *item = (gchar *) librdf_uri_as_string (uri);

      if (object)
	*object = RDF_OBJECT_RESOURCE;
      break;

      /* Leteral: */
    case LIBRDF_NODE_TYPE_LITERAL:
      *item = (gchar *) librdf_node_get_literal_value (node);

      if (object)
	*object = RDF_OBJECT_LITERAL;

      if (lang)
	*lang = (gchar *) librdf_node_get_literal_value_language (node);

      if (datatype)
	{
	  librdf_uri *datatype_uri;

	  if ((datatype_uri =
	       librdf_node_get_literal_value_datatype_uri (node)))
	    *datatype = (gchar *) librdf_uri_as_string (datatype_uri);
	}

      break;

      /* Blank node: */
    case LIBRDF_NODE_TYPE_BLANK:
      *item = (gchar *) librdf_node_get_blank_identifier (node);

      if (object)
	*object = RDF_OBJECT_BNODE;

      break;

    default:
      return FALSE;
    }

  if (!*item)
    return FALSE;

  *item = rdffs_parse_trim (*item);
  *itemsize = strlen (*item);

  if (lang && *lang)
    {
      *lang = rdffs_parse_trim (*lang);
      *langsize = strlen (*lang);
    }

  if (datatype && *datatype)
    {
      *datatype = rdffs_parse_trim (*datatype);
      *datatypesize = strlen (*datatype);
    }

  return TRUE;
}

static void
rdffs_parse_stream (librdf_stream * stream)
{
  /* For each statement... */
  while (!librdf_stream_end (stream))
    {
      GList *list;

      librdf_node *node;
      librdf_statement *statement;
      struct rdffs_triple_t *new;

      struct rdffs_t *subject_data;
      enum rdffs_object_t subject_type;
      gsize subject_size;
      gchar *subject;

      if (!(statement = librdf_stream_get_object (stream)))
	break;

      /* Subject: */
      node = librdf_statement_get_subject (statement);
      if (rdffs_parse_stream_node
	  (&subject, &subject_size, &subject_type, NULL, NULL, NULL, NULL,
	   node) == FALSE)
	continue;

      /* My data struct: */
      new = g_malloc0 (sizeof (struct rdffs_triple_t));

      /* Property: */
      node = librdf_statement_get_predicate (statement);
      if (rdffs_parse_stream_node
	  (&new->predicate, &new->predicate_size, NULL, NULL, NULL, NULL,
	   NULL, node) == FALSE)
	{
	  rdffs_triple_free (new);
	  g_free (subject);
	  continue;
	}

      /* Object: */
      node = librdf_statement_get_object (statement);
      if (rdffs_parse_stream_node
	  (&new->object, &new->object_size, &new->object_type, &new->lang,
	   &new->lang_size, &new->datatype, &new->datatype_size,
	   node) == FALSE)
	{
	  rdffs_triple_free (new);
	  g_free (subject);
	  continue;
	}

      if (subject_type == RDF_OBJECT_BNODE)
	list = rdffs_input.bnodes;
      else
	list = rdffs_input.subjects;

      for (; list; list = list->next)
	{
	  subject_data = list->data;

	  if (!g_utf8_collate (subject_data->subject, subject))
	    break;
	}

      if (!list)
	{
	  subject_data = g_malloc0 (sizeof (struct rdffs_t));

	  subject_data->subject = g_strdup (subject);
	  subject_data->subject_size = subject_size;

	  if (subject_type == RDF_OBJECT_BNODE)
	    {
	      rdffs_input.bnodes_size++;
	      rdffs_input.bnodes =
		g_list_prepend (rdffs_input.bnodes, subject_data);
	    }

	  else
	    {
	      rdffs_input.subjects_size++;
	      rdffs_input.subjects =
		g_list_prepend (rdffs_input.subjects, subject_data);
	    }
	}

      subject_data->triples = g_list_append (subject_data->triples, new);
      g_free (subject);

      /* Next statement: */
      librdf_stream_next (stream);
    }
}

static void
rdffs_parse_link (GList * list)
{
  for (; list; list = list->next)
    {
      struct rdffs_t *rdf = list->data;
      g_list_foreach (rdf->triples, (GFunc) rdffs_parse_triple_link, NULL);
    }
}

static void
rdffs_parse_triple_link (struct rdffs_triple_t *triple)
{
  rdffs_parse_resource_link (triple->predicate, &triple->predicate_link,
			     FALSE);

  if (triple->object_type != RDF_OBJECT_LITERAL)
    rdffs_parse_resource_link (triple->object, &triple->object_link,
			       triple->object_type ==
			       RDF_OBJECT_BNODE ? TRUE : FALSE);
  else if (triple->datatype)
    rdffs_parse_resource_link (triple->datatype, &triple->datatype_link,
			       FALSE);
}

static void
rdffs_parse_resource_link (gchar * string, struct rdffs_link_t *link,
			   gboolean is_bnode)
{
  GList *list;
  gint i;

  if (is_bnode == FALSE)
    list = rdffs_input.subjects;
  else
    list = rdffs_input.bnodes;

  for (i = 0; list; list = list->next, i++)
    {
      struct rdffs_t *rdf = list->data;

      if (!g_utf8_collate (rdf->subject, string))
	{
	  link->subject = rdf;
	  link->is_bnode = is_bnode;
	  link->number = i;

	  break;
	}
    }
}

/* WRITE *****************************************************************/
static void rdffs_write_list (GList * list, gboolean bnode,
			      librdf_world * world, librdf_model * model);

gboolean
rdffs_write (gchar * file, enum rdffs_format_t format, gchar ** buffer,
	     gsize * size, gchar ** error, gsize * error_size)
{
  librdf_world *world;
  librdf_storage *storage;
  librdf_model *model;
  librdf_uri *uri;
  GString *errstring = NULL;

  world = librdf_new_world ();
  librdf_world_open (world);

  librdf_world_set_error (world, &errstring, rdffs_parse_log);
  librdf_world_set_warning (world, &errstring, rdffs_parse_log);

  if (!(uri = librdf_new_uri_from_filename (world, file)))
    {
      librdf_free_world (world);
      return FALSE;
    }

  if (!(storage = librdf_new_storage (world, "memory", NULL, NULL)))
    {
      librdf_free_uri (uri);
      librdf_free_world (world);
      return FALSE;
    }

  if (!(model = librdf_new_model (world, storage, NULL)))
    {
      librdf_free_storage (storage);
      librdf_free_uri (uri);
      librdf_free_world (world);
      return FALSE;
    }

  rdffs_write_list (rdffs_input.subjects, FALSE, world, model);
  rdffs_write_list (rdffs_input.bnodes, TRUE, world, model);

  switch (format)
    {
    case RDF_FORMAT_NTRIPLES:
      *buffer =
	(gchar *) librdf_model_to_string (model, uri, "ntriples", NULL, NULL);
      break;

    case RDF_FORMAT_TURTLE:
      *buffer =
	(gchar *) librdf_model_to_string (model, uri, "turtle", NULL, NULL);
      break;

    case RDF_FORMAT_XML:
    default:
      *buffer =
	(gchar *) librdf_model_to_string (model, uri, "rdfxml", NULL, NULL);
      break;
    }

  if (!*buffer)
    {
      if (errstring)
	{
	  rdffs_destroy_data ();

	  *error_size = errstring->len;
	  *error = g_string_free (errstring, FALSE);
	  return FALSE;
	}

      *error = g_strdup ("Error creating the buffer");
      *error_size = strlen (*error);
      return FALSE;
    }

  *size = strlen (*buffer);

  librdf_free_model (model);
  librdf_free_storage (storage);
  librdf_free_uri (uri);
  librdf_free_world (world);

  return TRUE;
}

static void
rdffs_write_list (GList * list, gboolean bnode, librdf_world * world,
		  librdf_model * model)
{
  for (; list; list = list->next)
    {
      struct rdffs_t *rdf = list->data;
      GList *l;

      for (l = rdf->triples; l; l = l->next)
	{
	  struct rdffs_triple_t *triple = l->data;

	  librdf_statement *statement;
	  librdf_node *node;

	  statement = librdf_new_statement (world);

	  if (bnode == FALSE)
	    node =
	      librdf_new_node_from_uri_string (world,
					       (const unsigned char *) rdf->
					       subject);
	  else
	    node =
	      librdf_new_node_from_blank_identifier (world,
						     (const unsigned char *)
						     rdf->subject);

	  librdf_statement_set_subject (statement, node);

	  node =
	    librdf_new_node_from_uri_string (world,
					     (const unsigned char *) triple->
					     predicate);
	  librdf_statement_set_predicate (statement, node);

	  switch (triple->object_type)
	    {
	    case RDF_OBJECT_LITERAL:
	      if (!triple->datatype)
		node =
		  librdf_new_node_from_literal (world,
						(const unsigned char *)
						triple->object, triple->lang,
						0);

	      else
		{
		  librdf_uri *uri = librdf_new_uri (world,
						    (const unsigned char *)
						    triple->datatype);

		  node =
		    librdf_new_node_from_typed_literal (world,
							(const unsigned char
							 *) triple->object,
							triple->lang, uri);
		}

	      break;

	    case RDF_OBJECT_BNODE:
	      node =
		librdf_new_node_from_blank_identifier (world,
						       (const unsigned char *)
						       triple->object);
	      break;

	    case RDF_OBJECT_RESOURCE:
	    default:
	      node =
		librdf_new_node_from_uri_string (world,
						 (const unsigned char *)
						 triple->object);
	      break;
	    }

	  librdf_statement_set_object (statement, node);

	  librdf_model_add_statement (model, statement);
	  librdf_free_statement (statement);
	}
    }
}

/* FREE *******************************************************************/
void
rdffs_free (struct rdffs_t *rdf)
{
  if (!rdf)
    return;

  if (rdf->subject)
    g_free (rdf->subject);

  if (rdf->triples)
    {
      g_list_foreach (rdf->triples, (GFunc) rdffs_triple_free, NULL);
      g_list_free (rdf->triples);
    }

  g_free (rdf);
}

void
rdffs_triple_free (struct rdffs_triple_t *triple)
{
  if (!triple)
    return;

  if (triple->predicate)
    g_free (triple->predicate);

  if (triple->object)
    g_free (triple->object);

  if (triple->lang)
    g_free (triple->lang);

  if (triple->datatype)
    g_free (triple->datatype);

  g_free (triple);
}

/* EOF */
